/*
	gtp-rhino.cc	GRhino GTP Frontend
	Copyright (c) 2005, 2006, 2010 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "config.h"

#include <pthread.h>

#include <sys/types.h>
#include <time.h>
#include <sys/time.h>

#include <exception>
#include <stdexcept>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <deque>
#include <vector>
#include <sstream>

#include "board.h"
#include "hash.h"
#include "alphabeta.h"
#include "opening.h"
#include "pattern.h"
#include "parity.h"
#include "book.h"
#include "game.h"
#include "randboard.h"
#include "load.h"
#include "rand.h"
#include "boardio.h"
#include "gtp.h"

#include "gtstream.h"

// Grab version number in VERSION variable
#undef VERSION
const char *
#include "scripts/version"
;

#ifdef _
#undef _
#endif

#ifdef N_
#undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) x

const char *prog_name = "gtp-rhino";
const char *prog_ver = VERSION;

int	randomness;
double	komi;
bool	game_logged;
bool	is_log;

unsigned	main_time = 0;
unsigned	byo_yomi_time = 1;
unsigned	byo_yomi_stones = 0;

bool		use_byo_yomi = false;
unsigned	time_left_black;
unsigned	stone_left_black;
unsigned	time_left_white;
unsigned	stone_left_white;

// Implementation status (GTP version 2 draft 2)
//	2.1 done	2.2 done	2.3 done	2.4 done
//	2.5 done	2.6 done	2.7 done	2.8 done
//	2.9 done	2.10 done	2.11 N/A	2.12 done
//	2.13 done	2.14 done
//	3.1 done	3.2 done	3.3 done	3.4 done
//	3.5 done	3.6 done
//	4.1 N/A		4.2		4.3 done
//	5.1		5.2 done	5.3 done
//	6.1 done	6.2		6.3

void	gtp_new_game(const byte_board_info *b = 0, int color = BLACK)
{
	eval_new_game();
	if (!b)
		new_game(cur_game_info);
	else
		new_game(cur_game_info, *b, color);
	time_player = 0;
	game_logged = false;

	use_byo_yomi = false;
	time_left_black = main_time;
	time_left_white = main_time;
}

void	gtp_maybe_log_game()
{
	if (!cur_game_info.is_game_play() && !game_logged) {
		game_logged = true;
		log_history("grhino.log", "player", "player");
	}
}

typedef void (*gtp_func)(const std::string &, size_t i, bool has_id, unsigned id);

struct gtp_command {
	const char	*command;
	gtp_func	func;
};

extern gtp_command gtp_commands[];

void	gtpfunc_protocol_version(const std::string &/*str*/, size_t /*i*/,
				 bool has_id, unsigned id)
{
	output_response("2", has_id, id);
}

void	gtpfunc_name(const std::string &/*str*/, size_t /*i*/,
		     bool has_id, unsigned id)
{
	output_response("GTP GRhino", has_id, id);
}

void	gtpfunc_version(const std::string &/*str*/, size_t /*i*/,
			bool has_id, unsigned id)
{
	output_response(VERSION, has_id, id);
}

void	gtpfunc_known_command(const std::string &str, size_t i,
			      bool has_id, unsigned id)
{
	i = skip_space(str, i);
	if (i == str.size())
		output_response("false", has_id, id);
	else {
		size_t j = skip_non_space(str, i);
		int k;
		for (k = 0 ; gtp_commands[k].command; ++k) {
			if (!str.compare(i, j-i, gtp_commands[k].command))
				break;
		}
		if (gtp_commands[k].command)
			output_response("true", has_id, id);
		else
			output_response("false", has_id, id);
	}
}

void	gtpfunc_list_commands(const std::string &/*str*/, size_t /*i*/,
			      bool has_id, unsigned id)
{
	std::string output;
	for (int k = 0 ; gtp_commands[k].command; ++k) {
		if (k != 0)
			output += '\n';
		output += gtp_commands[k].command;
	}
	output_response(output, has_id, id);
}

void	gtpfunc_quit(const std::string &/*str*/, size_t /*i*/,
		     bool has_id, unsigned id)
{
	output_response("", has_id, id);
	throw command_quit();
}

void	gtpfunc_boardsize(const std::string &str, size_t i,
			   bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	unsigned num;
	i = read_unsigned(str, i, num);
	throw_if_extra_argument(str, i);
	gtp_new_game();
	if (num != 8)
		output_error("unacceptable size", has_id, id);
	else
		output_response("", has_id, id);
}

void	gtpfunc_clear_board(const std::string &/*str*/, size_t /*i*/,
			    bool has_id, unsigned id)
{
	gtp_new_game();
	output_response("", has_id, id);
}

void	gtpfunc_komi(const std::string &str, size_t i,
		     bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);
	i = read_float(str, i, komi);
	throw_if_extra_argument(str, i);
	output_response("", has_id, id);
}

void	gtpfunc_play(const std::string &str, size_t i,
		     bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	int	color;
	i = read_color(str, i, color);

	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	int	row, col, pos;
	size_t j = skip_non_space(str, i);
	if (j-i != 2)
		throw syntax_error();
	if (str[i] >= 'A' && str[i] <= 'H')
		col = str[i] - 'A';
	else if (str[i] >= 'a' && str[i] <= 'h')
		col = str[i] - 'a';
	else
		throw syntax_error();

	if (str[i+1] >= '1' && str[i+1] <= '8')
		row = str[i+1] - '1';
	else
		throw syntax_error();

	throw_if_extra_argument(str, j);

	pos = xy_to_pos(col, row);

	if (color == cur_game_info.get_player()
	    && cur_game_info.board_ptr->can_play(color, pos)) {
		// FIXME: Handle time
		cur_game_info.place_piece(pos, time_player);
		time_player = 0;
		gtp_maybe_log_game();
		output_response("", has_id, id);
	}
	else
		output_error("illegal move", has_id, id);
}

void	gtpfunc_genmove(const std::string &str, size_t i,
			bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	int	color;
	i = read_color(str, i, color);

	if (cur_game_info.is_game_play()
	    && color == cur_game_info.get_player()) {
		int pos = get_computer_move (komi);
		// FIXME: Handle time
		cur_game_info.place_piece(pos, time_player);
		time_player = 0;
		gtp_maybe_log_game();

		std::string output;
		output += pos_to_x(pos) + 'A';
		output += pos_to_y(pos) + '1';
		output_response(output, has_id, id);
	}
	else
		output_response("pass", has_id, id);
}

void	gtpfunc_reg_genmove(const std::string &str, size_t i,
			    bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	int	color;
	i = read_color(str, i, color);

	if (cur_game_info.is_game_play()
	    && color == cur_game_info.get_player()) {
		random_off();
		int pos = get_computer_move (komi);
		random_on();

		std::string output;
		output += pos_to_x(pos) + 'A';
		output += pos_to_y(pos) + '1';
		output_response(output, has_id, id);
	}
	else
		output_response("pass", has_id, id);
}

void	gtpfunc_auto_play(const std::string &/*str*/, size_t /*i*/,
			bool has_id, unsigned id)
{
	while (cur_game_info.is_game_play()) {
		int pos = get_computer_move (komi);
		// FIXME: Handle time
		cur_game_info.place_piece(pos, time_player);
	}
	gtp_maybe_log_game();
	output_response("", has_id, id);
}

void	gtpfunc_time_settings(const std::string &str, size_t i,
			      bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	unsigned m, b, s;

	i = read_unsigned(str, i, m);
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	i = read_unsigned(str, i, b);
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	i = read_unsigned(str, i, s);
	throw_if_extra_argument(str, i);

	main_time = m;
	byo_yomi_time = b;
	byo_yomi_stones = s;

	use_byo_yomi = false;
	time_left_black = main_time;
	time_left_white = main_time;
	time_player = 0;

	output_response("", has_id, id);
}

void	gtpfunc_time_left(const std::string &str, size_t i,
			  bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	int	color;
	i = read_color(str, i, color);

	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	unsigned t, s;

	i = read_unsigned(str, i, t);
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	i = read_unsigned(str, i, s);
	throw_if_extra_argument(str, i);

	if (color == BLACK) {
		time_left_black = t;
		stone_left_black = s;
	}
	else {
		time_left_white = t;
		stone_left_white = s;
	}
	time_player = 0;

	output_response("", has_id, id);
}

void	gtpfunc_final_score(const std::string &/*str*/, size_t /*i*/,
			    bool has_id, unsigned id)
{
	if (cur_game_info.is_game_play())
		output_error("cannot score", has_id, id);
	else {
		int	black_score = cur_game_info.board_ptr->board_black_score();
		int	white_score = cur_game_info.board_ptr->board_white_score();

		switch (cur_game_info.get_game_result()) {
			case game_info::game_result_end:
				adjust_score(black_score, white_score);
				break;
			case game_info::game_result_timeout_black:
				black_score = 0;
				white_score = 64;
				break;
			case game_info::game_result_timeout_white:
				black_score = 64;
				white_score = 0;
				break;
			case game_info::game_result_resign_black:
				black_score = 0;
				white_score = 64;
				break;
			case game_info::game_result_resign_white:
				black_score = 64;
				white_score = 0;
				break;
		}

		std::ostringstream os;
		double diff_score = black_score - white_score - komi;
		if (diff_score > 0)
			os << "B+" << diff_score;
		else if (diff_score < 0)
			os << "W+" << (-diff_score);
		else
			os << '0';
		output_response(os.str(), has_id, id);
	}
}

void	gtpfunc_showboard(const std::string &/*str*/, size_t /*i*/,
			  bool has_id, unsigned id)
{
	std::ostringstream os;

	if (cur_game_info.is_game_play()) {
		os << "Next move: ";
		if (cur_game_info.get_player() == BLACK)
			os << "Black";
		else
			os << "White";
	}
	else {
		switch (cur_game_info.get_game_result()) {
			case game_info::game_result_end:
				os << "Game ended";
				break;
			case game_info::game_result_timeout_black:
				os << "Black ran out of time";
				break;
			case game_info::game_result_timeout_white:
				os << "White ran out of time";
				break;
			case game_info::game_result_resign_black:
				os << "Black resigned";
				break;
			case game_info::game_result_resign_white:
				os << "White resigned";
				break;
		}
	}
	os << '\n';
	print_board(os, cur_game_info.board_ptr, 2);

	output_response(os.str(), has_id, id);
}

void	gtpfunc_undo(const std::string &/*str*/, size_t /*i*/,
		     bool has_id, unsigned id)
{
	if (cur_game_info.is_undoable()) {
		cur_game_info.undo();
		game_logged = false;
		output_response("", has_id, id);
	}
	else
		output_error("cannot undo", has_id, id);
}

void	gtpfunc_set_game(const std::string &str, size_t i,
			 bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	size_t j = skip_non_space(str, i);
	if (!str.compare(i, j-i, "Othello"))
		output_response("", has_id, id);
	else
		output_error("unsupported game", has_id, id);
}

void	gtpfunc_list_games(const std::string &/*str*/, size_t /*i*/,
			   bool has_id, unsigned id)
{
	output_response("Othello", has_id, id);
}

void	gtpfunc_setup_board(const std::string &str, size_t i,
			    bool has_id, unsigned id)
{
	i = skip_space(str, i);
	throw_syntax_error_if_end_of_line(str, i);

	size_t j = skip_non_space(str, i);
	if (j-i != 64)
		throw syntax_error();
	j = skip_space(str, j);
	int	color = BLACK;
	if (j != str.size()) {
				// Optional player with first move
		size_t jj = skip_non_space(str, j);
		throw_if_extra_argument(str, jj);
		if (jj-j == 1) {
			switch (str[j]) {
				case 'O':
				case 'o':
				case '0':
				case 'W':
				case 'w':
					color = WHITE;
					break;
				case '@':
				case '*':
				case 'X':
				case 'x':
				case 'B':
				case 'b':
					// Default is already black
					break;
				default:
					throw syntax_error();
			}
		}
		else
			read_color(str, j, color);
	}

	byte_board_type t;
	int num_pieces = 0;
	for (int k = 0; k < 64; ++k) {
		piece_type p;
		switch (str[i+k]) {
			case '_':
			case '-':
			case '.':
				p = EMPTY;
				break;
			case 'O':
			case 'o':
			case '0':
				p = WHITE;
				num_pieces++;
				break;
			case '@':
			case '*':
			case 'X':
			case 'x':
				p = BLACK;
				num_pieces++;
				break;
			default:
				throw syntax_error();
		}
		t[k] = p;
	}
	if (num_pieces < 4)
		throw syntax_error();

	byte_board_info b(&t);
	gtp_new_game(&b, color);
	output_response("", has_id, id);
}

void	gtpfunc_show_history(const std::string &/*str*/, size_t /*i*/,
			     bool has_id, unsigned id)
{
	std::string output;
	for (int i = 0; i < cur_game_info.num_history-1; ++i) {
		int pos = cur_game_info.move_history[i];
		if (i != 0) {
			output += ' ';
			if (cur_game_info.player_history[i-1]
			    == cur_game_info.player_history[i])
				output += "pass ";
		}
		else {
			if (cur_game_info.first_play_is_pass)
				output += "pass ";
		}

		output += pos_to_x(pos) + 'A';
		output += pos_to_y(pos) + '1';
	}
	output_response(output, has_id, id);
}

gtp_command gtp_commands[] = {
	{ "protocol_version", gtpfunc_protocol_version },
	{ "name", gtpfunc_name },
	{ "version", gtpfunc_version },
	{ "known_command", gtpfunc_known_command },
	{ "list_commands", gtpfunc_list_commands },
	{ "quit", gtpfunc_quit },
	{ "boardsize", gtpfunc_boardsize },
	{ "clear_board", gtpfunc_clear_board },
	{ "komi", gtpfunc_komi },
					// Not supported: fixed_handicap,
					// place_free_handicap,
					// set_free_handicap
	{ "play", gtpfunc_play },
	{ "genmove", gtpfunc_genmove },
	{ "undo", gtpfunc_undo },
					// Not supported:
					// final_status_list, load_sgf
	{ "time_settings", gtpfunc_time_settings },
	{ "time_left", gtpfunc_time_left },
	{ "final_score", gtpfunc_final_score },
	{ "showboard", gtpfunc_showboard },
	{ "reg_genmove", gtpfunc_reg_genmove },

					// Othello extension
	{ "set_game", gtpfunc_set_game },
	{ "list_games", gtpfunc_list_games },

					// GTP-Rhino extension
	{ "grhino-auto_play", gtpfunc_auto_play },
	{ "grhino-setup_board", gtpfunc_setup_board },
	{ "grhino-show_history", gtpfunc_show_history },
	{ 0, 0 }
};

void	gtp_process_loop()
{
	std::string	str;

	bool		has_id;
	unsigned	id;
	size_t		i;

	for ( ; ; ) {
		try {
			input_line(str);
			i = parse_id(str, has_id, id);
			i = skip_space(str, i);
			throw_command_error_if_end_of_line(str, i);

			size_t j = skip_non_space(str, i);
			int k;
			for (k = 0; gtp_commands[k].command; ++k) {
				if (!str.compare(i, j-i, gtp_commands[k].command)) {
					gtp_commands[k].func(str, j, has_id, id);
					break;
				}
			}
			if (!gtp_commands[k].command)
				throw command_error();
		}
		catch (id_error &) {
			output_error("unknown command", false, 0);
		}
		catch (command_error &) {
			output_error("unknown command", has_id, id);
		}
		catch (syntax_error &) {
			output_error("syntax error", has_id, id);
		}
		catch (command_quit &) {
			return;
		}
		catch (io_error &) {
			return;
		}
	}
}

void	set_level(int level)
{
	computer_level = level;
	current_level_info.midgame_depth
		= level_info[computer_level].midgame_depth;
	current_level_info.num_empty_winlossdraw
		= level_info[computer_level].num_empty_winlossdraw;
	current_level_info.num_empty_exact
		= level_info[computer_level].num_empty_exact;
	set_midgame_depth(current_level_info.midgame_depth);
}

void	init()
{
	trans_table_init();
	set_max_hash_move(57);

	pattern_table_init();
	book_init();
	srand(time(NULL));

	set_level(1);			// Default level

					// Default parameters
	randomness = 0;
	set_eval_random(randomness);
	opening_var = 2;
	set_book_random(opening_var);
	log_move = false;
	redo_ai_move = true;
	start_game_mode = START_GAME_INITIAL;

	is_log = false;
}

int	main_real(int argc, char *argv[])
{
					// Need to parse parameter
	if (argc > 1) {
		for (int i = 1; i < argc; ++i) {
			std::string arg = argv[i];
			unsigned u;

			if (arg == "-h" || arg == "--help") {
				std::cout << prog_name << ' ' << prog_ver << _(" - GTP Frontend for Rhino\n");
				std::cout << _("(c) 2005, 2006, 2010 Kriang Lerdsuwanakij\n\n");
				gtout(std::cout, _("Usage:   %$ [options]\n")) << prog_name;
				std::cout << _("Available options:\n");
				std::cout << _("     -b N, --book=N    Use open book variation N.  Allowed = 0..5 (means None..Very high).\n");
				std::cout << _("                       Default = 2 (means Low)\n");
				std::cout << _("     -e N, --end=N     Use perfect end game search depth N instead of default value for\n");
				std::cout << _("                       level.  Allowed = 1..20.\n");
				std::cout << _("     -h, --help        Display this help\n");
				std::cout << _("     -l N, --level=N   Use AI level N.  Allowed = 1..5.  Default = 3\n");
				std::cout << _("     --log             Log game to ~/grhino.log\n");
				std::cout << _("     -m N, --mid=N     Use mid game search depth N instead of default value for level.\n");
				std::cout << _("                       Allowed = 1..20\n");
				std::cout << _("     -r N, --rand=N    Use randomness N in AI evaluator.  Allowed = 0..10.  Default = 0\n");
				std::cout << _("     -v, --version     Display version number\n");
				std::cout << _("     -w N, --win=N     Use winning move search depth N instead of default value for level.\n");
				std::cout << _("                       Allowed = 1..20\n\n");
				return 0;
			}
			else if (arg == "-v" || arg == "--version") {
				std::cout << prog_name << ' ' << prog_ver << '\n';
				return 0;
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-l", "--level",
							 1, 5, u)) {
				set_level(u - 1);
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-m", "--mid",
							 1, 20, u)) {
				current_level_info.midgame_depth = u;
				set_midgame_depth(current_level_info.midgame_depth);
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-e", "--end",
							 1, 20, u)) {
				current_level_info.num_empty_exact = u;
				if (current_level_info.num_empty_winlossdraw
				    < static_cast<int>(u))
					current_level_info.num_empty_winlossdraw = u;
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-w", "--win",
							 1, 20, u)) {
				current_level_info.num_empty_winlossdraw = u;
				if (current_level_info.num_empty_exact
				    > static_cast<int>(u))
					current_level_info.num_empty_exact = u;
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-r", "--rand",
							 0, 10, u)) {
				randomness = u;
				set_eval_random(randomness);
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-b", "--book",
							 0, 5, u)) {
				opening_var = u;
				set_book_random(opening_var);
			}
			else if (arg == "--log")
				is_log = true;
			else {
				gtstream bufstr;
				gtout(bufstr, _("unknown option `%$\'")) <<  arg;
				throw std::runtime_error(bufstr.str());
			}
		}
	}

	try {
		gtp_new_game();
		gtp_process_loop();
	}
	catch (...) {
	}

	return 0;
}

int	main(int argc, char *argv[])
{
	try {
		init();
		return main_real(argc, argv);
	}
	catch (std::exception &e) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: %$\n")) << prog_name << e.what();
	}
	catch (const char *s) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: exception %$\n")) << prog_name << s;
	}
	catch (...) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: unknown exception\n")) << prog_name;
	}
	return 1;
}
